use actix_web::{web, HttpResponse};
use serde_json::Value;
use wither::bson::{doc, Bson};

use crate::{
    models::{message::MessageModelDTO, user::NormalUserModel},
    services::{
        club_service::ClubService, message_service::MessageService, user_service::UserService,
    },
    utils::{
        db_utils::{build_filter, COMMON_NO_REGEXP},
        path_params::PathParamMDB,
    },
    AppResult, PaginationQuery,
};

mod ctrl_struct {
    use std::str::FromStr;

    use crate::{
        models::message::MessageReceiver,
        models::user::SimpleUserModel,
        services::{
            club_service::ClubService, message_service::CreateMessageReq, user_service::UserService,
        },
        utils::db_utils::json_to_bson,
        AppResult,
    };
    use serde::{Deserialize, Serialize};
    use serde_json::Value;
    use wither::bson::oid::ObjectId;
    #[derive(Debug, Clone, Serialize, Deserialize)]
    #[serde(tag = "type")]
    pub enum SendMessageReq {
        #[serde(rename = "club")]
        Club {
            title: String,
            content: String,
            club_id: String,
            #[serde(default = "Default::default")]
            extra: Value,
        },
        #[serde(rename = "pointer")]
        Pointer {
            receiver_id: String,
            title: String,
            content: String,
            #[serde(default = "Default::default")]
            extra: Value,
        },
        #[serde(rename = "comment")]
        Comment {
            title: String,
            content: String,
            club_id: String,
            #[serde(default = "Default::default")]
            extra: Value,
        },
        #[serde(rename = "reply")]
        Reply {
            receiver_id: String,
            parent_id: String,
            title: String,
            content: String,
            #[serde(default = "Default::default")]
            extra: Value,
        },
    }

    impl SendMessageReq {
        pub async fn into_create_message_req<T: Into<SimpleUserModel>>(
            self,
            club_service: &ClubService,
            user_service: &UserService,
            sender: T,
        ) -> AppResult<CreateMessageReq> {
            match self {
                SendMessageReq::Club {
                    title,
                    content,
                    club_id,
                    extra,
                } => {
                    let oid = ObjectId::from_str(&club_id)?;
                    let club = club_service.club_detail(oid).await?;
                    Ok(CreateMessageReq {
                        sender: Some(sender.into()),
                        title,
                        content,
                        receiver: MessageReceiver::Club(club.into()),
                        extra: Some(json_to_bson(&extra)),
                    })
                }
                SendMessageReq::Pointer {
                    receiver_id,
                    title,
                    content,
                    extra,
                } => {
                    let receiver_id = ObjectId::from_str(&receiver_id)?;
                    let receiver = user_service.user_detail(receiver_id).await?;
                    Ok(CreateMessageReq {
                        sender: Some(sender.into()),
                        title,
                        content,
                        receiver: MessageReceiver::Pointer(receiver.into()),
                        extra: Some(json_to_bson(&extra)),
                    })
                }
                SendMessageReq::Comment {
                    title,
                    content,
                    club_id,
                    extra,
                } => {
                    let club_oid = ObjectId::from_str(&club_id)?;
                    let club = club_service.club_detail(club_oid).await?;
                    Ok(CreateMessageReq {
                        sender: Some(sender.into()),
                        title,
                        content,
                        receiver: MessageReceiver::Comment(club.into()),
                        extra: Some(json_to_bson(&extra)),
                    })
                }
                SendMessageReq::Reply {
                    receiver_id,
                    parent_id,
                    title,
                    content,
                    extra,
                } => {
                    let receiver_id = ObjectId::from_str(&receiver_id)?;
                    let parent_id = ObjectId::from_str(&parent_id)?;
                    user_service.user_detail(receiver_id).await?;
                    Ok(CreateMessageReq {
                        sender: Some(sender.into()),
                        receiver: MessageReceiver::Reply {
                            id: parent_id,
                            user_id: receiver_id,
                        },
                        title,
                        content,
                        extra: Some(json_to_bson(&extra)),
                    })
                }
            }
        }
    }
}

async fn send_message(
    message_service: web::Data<MessageService>,
    club_service: web::Data<ClubService>,
    user_service: web::Data<UserService>,
    user: NormalUserModel,
    req: web::Json<ctrl_struct::SendMessageReq>,
) -> AppResult<HttpResponse> {
    let create_req = req
        .into_inner()
        .into_create_message_req(&club_service, &user_service, user)
        .await?;
    let res = message_service.create_message(create_req).await?;
    Ok(HttpResponse::Ok().json(MessageModelDTO::from(res)))
}

async fn find_my_sended_message(
    message_service: web::Data<MessageService>,
    user: NormalUserModel,
    query: web::Query<PaginationQuery>,
) -> AppResult<HttpResponse> {
    let filter = doc! {
        "sender.id":user.id
    };

    let res = message_service.find_all_message(filter, &query).await?;

    Ok(HttpResponse::Ok().json(res.map_into::<MessageModelDTO>()))
}

async fn search_my_sended_message(
    message_service: web::Data<MessageService>,
    user: NormalUserModel,
    query: web::Query<PaginationQuery>,
    where_value: web::Json<Option<Value>>,
) -> AppResult<HttpResponse> {
    let mut filter = build_filter(
        &where_value.into_inner().unwrap_or(Value::Null),
        &COMMON_NO_REGEXP,
    )
    .unwrap_or_default();
    filter.insert("sender.id", user.id);

    let res = message_service.find_all_message(filter, &query).await?;

    Ok(HttpResponse::Ok().json(res.map_into::<MessageModelDTO>()))
}

async fn find_my_received_message(
    message_service: web::Data<MessageService>,
    user: NormalUserModel,
    query: web::Query<PaginationQuery>,
) -> AppResult<HttpResponse> {
    let filter = doc! {
        "receiver.id":user.id
    };
    let res = message_service.find_all_message(filter, &query).await?;
    Ok(HttpResponse::Ok().json(res.map_into::<MessageModelDTO>()))
}

async fn search_my_received_message(
    message_service: web::Data<MessageService>,
    user: NormalUserModel,
    query: web::Query<PaginationQuery>,
    where_value: web::Json<Option<Value>>,
) -> AppResult<HttpResponse> {
    let mut filter = build_filter(
        &where_value.into_inner().unwrap_or(Value::Null),
        &COMMON_NO_REGEXP,
    )
    .unwrap_or_default();

    let or_arr = vec![
        Bson::Document(doc! {
            "receiver.id":user.id
        }),
        Bson::Document(doc! {
            "receiver.user_id":user.id
        }),
    ];
    filter.insert("$or", or_arr);

    let res = message_service.find_all_message(filter, &query).await?;
    Ok(HttpResponse::Ok().json(res.map_into::<MessageModelDTO>()))
}

async fn mark_message_to_read(
    message_service: web::Data<MessageService>,
    user: NormalUserModel,
    path_params: PathParamMDB,
) -> AppResult<HttpResponse> {
    let message_id = get_path_param!(path_params, message_id, MSG_REQUIRE_MESSAGE_ID_IN_PATH);
    message_service
        .mark_message_to_read(message_id, user)
        .await?;
    Ok(HttpResponse::NoContent().finish())
}

pub fn configure_message_controller(config: &mut web::ServiceConfig) {
    config
        .service(
            web::resource("/send")
                .route(web::post().to(send_message))
                .route(web::get().to(find_my_sended_message)),
        )
        .service(web::resource("/send/search").route(web::post().to(search_my_sended_message)))
        .service(web::resource("/received").route(web::route().to(find_my_received_message)))
        .service(
            web::resource("/received/search").route(web::route().to(search_my_received_message)),
        )
        .service(
            web::scope("/single/{message_id}")
                .service(web::resource("/mark").route(web::post().to(mark_message_to_read))),
        );
}
